#include "./scan_uart.h"
#include "../xlib/gpio.h"
#include "../xlib/uart.h"
#include "../xlib/dma.h"
#include "../rtos/event_flags.h"
#include "../rtos/message_queue.h"

namespace cargo_driver {

ScanUart scanUart;

namespace scan_uart_impl {

using namespace stm32f1;

static constexpr uint16_t RECV_BUF_SIZE = 64;

struct DmaRecvBuf {
    uint8_t buf[RECV_BUF_SIZE];
    uint8_t copy_buf[RECV_BUF_SIZE];
    uint16_t index = 0;
};

using TX_PIN = gpio::PA<9>;
using RX_PIN = gpio::PA<10>;
using UART = uart::Uart1;
using TX_CHAN = typename dma::DmaSel<UART::BASE_VAL>::TX_CHAN;
using RX_CHAN = typename dma::DmaSel<UART::BASE_VAL>::RX_CHAN;

static constexpr uint32_t FLAG_TC = 0x01;

static DmaRecvBuf recvBuf;
static rtos::lazy::EventFlags eventFlags;
static rtos::lazy::MessageQueue<ByteView, 2> msgQueue;

struct ScanDef : public uart::DefCfg {
    static constexpr uint32_t BAUD_RATE = 9600;
};

}

using namespace scan_uart_impl;

void ScanUart::init() {

    eventFlags.init();
    msgQueue.init();

    rcc::enable<
        TX_PIN, RX_PIN,
        UART, TX_CHAN, RX_CHAN
    >();

    gpio::config<
        gpio::Cfg<TX_PIN, gpio::Mode::OUT_50MHz_AF_PP>,
        gpio::Cfg<RX_PIN, gpio::Mode::INPUT_FLOAT>
    >();

    UART::set_cfg<ScanDef>();

    RX_CHAN::set_cfg<dma::UartRecvCfg, UART::DR_ADDR_VAL>(
        reinterpret_cast<uint32_t>(recvBuf.buf),
        RECV_BUF_SIZE
    );

    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_EnableIRQ(DMA1_Channel4_IRQn);
}

static void write(const uint8_t *buf, usize_t len) {
    TX_CHAN::set_cfg<dma::UartWriteCfg, UART::DR_ADDR_VAL>(
        reinterpret_cast<uint32_t>(buf),
        len
    );
    eventFlags.wait_any_flags(FLAG_TC);
}

Error ScanUart::scan(ByteView &bw) {

    constexpr uint8_t SCAN_BYTES[] = { 0x1B, 0x31 };

    msgQueue.reset();

    write(SCAN_BYTES, 2);

    if (msgQueue.poll(bw, 500) != osOK) {
        return ec::cargo::SCAN_TIMEOUT;
    }

    if (msgQueue.poll(bw, 3000) != osOK) {
        return ec::cargo::SCAN_TIMEOUT;
    }
    if (bw.len < 2) {
        bw.len = 0;
        return ec::cargo::SCAN_FAIL;
    }
    bw.len -= 2;

    return ec::pub::OK;
}

static void recvIdle() {
    uint16_t index = RECV_BUF_SIZE - RX_CHAN::ndtr();
    if (index == recvBuf.index) {
        return;
    }
    ByteView bw;
    if (index > recvBuf.index) {
        bw.buf = recvBuf.buf + recvBuf.index;
        bw.len = index - recvBuf.index;
    } else {
        uint16_t k = RECV_BUF_SIZE - recvBuf.index;
        memcpy(recvBuf.copy_buf, recvBuf.buf + recvBuf.index, k);
        memcpy(recvBuf.copy_buf + k, recvBuf.buf, index);
        bw.buf = recvBuf.copy_buf;
        bw.len = k + index;
    }
    recvBuf.index = index;
    msgQueue.post(bw);
}

}

using namespace cargo_driver;

extern "C"
void DMA1_Channel4_IRQHandler() {
    if (TX_CHAN::is_tc()) {
        TX_CHAN::clear_flags();
        eventFlags.set_flags(FLAG_TC);
    }
}

extern "C"
void USART1_IRQHandler() {
    if (UART::is_idle()) {
        UART::clear_idle_flag();
        recvIdle();
    }    
}

